/*  Copyright 2008 Rene Weiskircher 

    This file is part of SUPA.

    SUPA is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    SUPA is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with SUPA.  If not, see <http://www.gnu.org/licenses/>.
 */
/**
 * Main class file for "Speeding Up Povray Animations" using multiple
 * processors or cores
 * 
 * @author Rene Weiskircher (rene.weiskircher@gmail.com)
 * 
 *  T
 */
package supa;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.util.LinkedList;
import java.util.Queue;

import supa.PovrayProcessor.ProcessFailedException;
import supa.PovrayProcessor.ProcessNotReadyException;

/**
 * @author Rene Weiskircher (rene.weiskircher@gmail.com)
 */
 public class Supa {
	/**
	 * Exception class for argument errors
	 *
	 */
	public class ArgumentException extends Exception {
		/**
		 * Constructor sets the message string
		 * @param arg0 the message
		 */
		public ArgumentException(String arg0) {
			super(arg0);
		}
	}
	/**
	 * path to the povray executable
	 */
	private String _povrayExecutable;

	/**
	 * Name of the Povray ini file
	 */
	File iniFile;
	/**
	 * Number of the first frame to be rendered
	 */
	int startFrame;
	/**
	 * Number of the last frame to be rendered
	 */
	int endFrame;
	/**
	 * Array of virtual processors
	 */
	PovrayProcessor[] procs;
	/**
	 * the string in the povray ini-file indicating the number of the first frame
	 */
	final static String firstFrameString = "Initial_Frame";
	/**
	 * the string in the povray ini-file indicating the number of the las frame
	 */
	final static String lastFrameString = "Final_Frame";
	/**
	 * Time in milliseconds between checks on the processes
	 */
	private static final long checkingInterval = 100;
	/**
	 * Prefix to the processor id
	 */
	private static final String processorPrefix = "Proc_";

	/**
	 * everything will be printed out to this
	 */
	private PrintStream _out;

	/**
	 * the output thread
	 */
	private ShellOutput _so;
	
	/**
	 * Parses the command line arguments and sets the name of the
	 * ini-file and the number of processes
	 * @param args Command line arguments
	 * @throws ArgumentException
	 * @return Number of processes desired
	 */
	private int parseInput(String[] args) throws ArgumentException {
		if(args.length < 3)
			throw new ArgumentException("Need povray executable, name of ini-file and number of processes as argument!");
		_povrayExecutable = args[0];
		iniFile = new File(args[1]);
		int processes = 0;
		try {
			processes = Integer.parseInt(args[2]);
		}
		catch(NumberFormatException nfe) {
			throw new ArgumentException("The second argument does not seem to be an integer!");
		}
		if(processes < 1) {
			throw new ArgumentException("Can't have less than one process!");
		}
		return processes;
	}
	/**
	 * Initializes the class 
	 * @param args Command line arguments
	 * @throws ArgumentException
	 * @throws IOException 
	 */
	public Supa(String[] args) throws ArgumentException, IOException{
			int processes = parseInput(args);
			setFrameRange();
			procs = new PovrayProcessor[processes];
			PovrayProcessor.setFiles(_povrayExecutable,iniFile);
			Queue<PovrayProcessor.ProcessEvent> q= new LinkedList<PovrayProcessor.ProcessEvent>();
			_out = System.out;
			_so = new ShellOutput(q,_out);
			_so.start();
			for(int i = 0; i < procs.length; i++) {
				procs[i] = new PovrayProcessor(i,q);
			}
	}
	/**
	 * Extracts the the range of frames to be rendered from the
	 * ini-file
	 * @throws IOException 
	 */
	void setFrameRange() throws IOException {
		//first we load the ini-file
		BufferedReader in = new BufferedReader(new FileReader(iniFile));
		//we look for the lines determining the first and last frame
		startFrame = -1;
		endFrame = -1;
		String lastString="";
		while(lastString != null && (startFrame < 0 || endFrame < 0)) {
			lastString = in.readLine();
			if(lastString != null) {
				if(lastString.contains(firstFrameString)) {
					String[] contents = lastString.split("=");
					startFrame = Integer.parseInt(contents[1]);
				}
				else {
					if(lastString.contains(lastFrameString)) {
						String[] contents = lastString.split("=");
						endFrame = Integer.parseInt(contents[1]);
					}
				}
			}
		}
	}
	
	
	/**
	 * Renders all frames by dividing them on the virtual processors
	 * @throws IOException 
	 * @throws ProcessNotReadyException 
	 * @throws ProcessFailedException 
	 */
	private void render() throws ProcessFailedException, ProcessNotReadyException, IOException {
		/* print the names of the processors */
		PrintStream out = System.out;
		for(PovrayProcessor p:procs)
			out.print(processorPrefix+Integer.toString(p.getId())+"\t");
		out.println();
		out.flush();
		int nextFrame = startFrame;
		try {
			while(nextFrame <= endFrame) {
				for(PovrayProcessor p:procs) {
					if(p.idle()) {
						p.renderFrame(nextFrame);
						nextFrame ++;
					}
				}
				Thread.sleep(checkingInterval);
			}
			/* at this point, all frames have been distributed, we just
			 * have to wait until they are all finished
			 */
			boolean allIdle = false;
			while(!allIdle) {
				allIdle = true;
				for(PovrayProcessor p:procs) {
					if(!p.idle()) {
						allIdle = false;
						break;
					}
				}
				if(!allIdle)
					Thread.sleep(checkingInterval);
			}
		}
		catch(InterruptedException ie) {
			System.err.println("Interrupted! Frames done: " + Integer.toString(nextFrame-1));
			System.err.flush();
			System.exit(1);
		}
		_so.retire();
	}

	/**
	 * @param args Absolute path of povray executable, povray ini-file and he number of processes that should be run in parallel
	 */
	public static void main(String[] args) {
		Supa supa = null;
		try {
			supa = new Supa(args);
		}
		catch(ArgumentException ex) {
			PrintStream err = System.err;
			err.println(ex.getMessage());
			err.flush();
			System.exit(1);
		}
		catch(FileNotFoundException fnf) {
			PrintStream err = System.err;
			err.println("Cannot open ini-file: " + fnf.getMessage());
			err.flush();
			System.exit(1);			
		}
		catch(IOException io) {
			PrintStream err = System.err;
			err.println("I/O exception reading from ini-file: " + io.getMessage());
			err.flush();
			System.exit(1);			
			
		}
		catch(NumberFormatException no) {
			PrintStream err = System.err;
			err.println("Problem reading numbers of start of end frame: " + no.getMessage());
			err.flush();
			System.exit(1);						
		}
		try {
			if(supa != null) //gets rid of a warning
				supa.render();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

}
